﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Nate_sRoguelikeGameXNA.GameObjects.Architecture;
using Nate_sRoguelikeGameXNA.Levels;

namespace Nate_sRoguelikeGameXNA.Levels
{

    class Level
    {
        # region Instance
        # region Fields

        private List<SplitRectangle>[] splitRectangles;
        private MapBlock[][] map;
        public Rectangle Dimensions { get; private set; }
        public GameArchitecture BaseArchitecture { get; private set; }
        public int Depth { get; private set; }
        public List<MapBlock> ChangedBlocks { get; private set; }
        public Level LevelAbove { get; private set; }
        public Level LevelBelow { get; private set; }
        public int[] RoomCountsBySize = new int[] { 0, 0, 0 };
        public List<Room> Rooms { get; set; }
        public List<Corridor> Corridors { get; set; }
        # endregion

        # region Constructor

        public Level(string name, Rectangle dimensions, GameArchitecture baseArchitecture)
        {
            Dimensions = dimensions;
            map = new MapBlock[dimensions.Width + 1][];
            Depth = 0;
            ChangedBlocks = new List<MapBlock>();
            BaseArchitecture = baseArchitecture;

            // Generate level full of baseArchitecture
            for (int x = 0; x < Dimensions.Width + 1; x++)
            {
                var line = new MapBlock[Dimensions.Height + 1];
                for (int y = 0; y < Dimensions.Height + 1; y++)
                    line[y] = new MapBlock(this, new Point(x, y)) { Architecture = baseArchitecture };
                map[x] = line;
            }
            Rooms = new List<Room>();
            Corridors = new List<Corridor>();
        }

        # endregion

        # region Methods

        public void Draw(SpriteBatch spriteBatch)
        {
            foreach (MapBlock block in map.SelectMany(blocks => blocks))
                block.Draw(spriteBatch);

            //foreach (Corridor corridor in Corridors)
            //    corridor.Draw(spriteBatch);
            foreach (Room room in Rooms)
                room.Draw(spriteBatch);
            //ChangedBlocks.Clear();
            //foreach (var rectangle in splitRectangles.SelectMany(r => r).Where(r => r.SubRectangles.Length == 2))
            //    rectangle.Source.Draw(spriteBatch, '>', Color.DeepPink);
            foreach (var rectangle in splitRectangles.SelectMany(r => r).Where(r => r.SubRectangles.Length == 0))
                rectangle.Source.Draw(spriteBatch, '>', Color.Orange);
        }

        public MapBlock GetMapBlockAt(int x, int y)
        {
            return GetMapBlockAt(new Point(x, y));
        }

        public MapBlock GetMapBlockAt(Point p)
        {
            return (p.X >= map.Length || p.X < 0 || p.Y >= map[0].Length || p.Y < 0)
                       ? null
                       : map[p.X][p.Y];
        }

        public IEnumerable<MapBlock> GetSurroundingPassableMapBlocks(GameCharacter player)
        {
            return GetSurroundingPassableMapBlocks(player, true);
        }

        public IEnumerable<MapBlock> GetSurroundingPassableMapBlocks(GameCharacter character, bool includeDiagonals)
        {
            return GetSurroundingMapBlocks(character.MapBlock.Location, includeDiagonals).Where(b => b.IsPassableBy(character));
        }

        public IEnumerable<MapBlock> GetSurroundingMapBlocks(Point location)
        {
            return GetSurroundingMapBlocks(location, true);
        }

        public IEnumerable<MapBlock> GetSurroundingMapBlocks(Point location, bool includeDiagonals)
        {

            List<MapBlock> blocks = new List<MapBlock>
                                        {
                                            // East, West, South, North
                                            map[Math.Min(map.Length - 1, location.X + 1)][location.Y],
                                            map[Math.Max(0, location.X - 1)][location.Y],
                                            map[location.X][Math.Min(map[0].Length - 1, location.Y + 1)],
                                            map[location.X][Math.Max(0, location.Y - 1)]
                                        };
            // Diagonals (Northwest, Southwest, Northeast, Southeast)
            if (includeDiagonals)
            {
                blocks.Add(map[Math.Max(0, location.X - 1)][Math.Max(0, location.Y - 1)]);
                blocks.Add(map[Math.Max(0, location.X - 1)][Math.Min(map[0].Length - 1, location.Y + 1)]);
                blocks.Add(map[Math.Min(map.Length - 1, location.X + 1)][Math.Max(0, location.Y - 1)]);
                blocks.Add(map[Math.Min(map.Length - 1, location.X + 1)][Math.Min(map[0].Length - 1, location.Y + 1)]);
            }
            // Cycle through all the surrounding blocks
            for (int x1 = Math.Max(0, location.X - 1); x1 <= Math.Min(map.Length - 1, location.X + 1); x1++)
            {
                for (int y1 = Math.Max(0, location.Y - 1); y1 <= Math.Min(map[0].Length - 1, location.Y + 1); y1++)
                {
                    // Don't add the current block, only the surrounding ones
                    if (x1 == location.X && y1 == location.Y)
                    {
                        continue;
                    }
                    blocks.Add(map[x1][y1]);
                }
            }
            return blocks;
        }

        # endregion
        # endregion
        # region Static
        # region Methods
        public static Level GenerateLevel(string name, Rectangle dimensions, GameArchitecture initialBaseArchitecture, GameArchitecture baseArchitecture, GameArchitecture wallArchitecture, bool withStairsUp, bool withStairsDown, Level lastLevel)
        {
            var level = new Level(name, dimensions, initialBaseArchitecture);
            level.BaseArchitecture = baseArchitecture;
            Random random = new Random();

            int numberOfRooms = (Game.Roll(14, 18) + 48) / 10;
            int minRoomSize = 4;
            Point maxRoomSize = new Point(dimensions.Width - (4 * (Math.Min(numberOfRooms, 2) - 1)),
                                          dimensions.Height - (2 * (Math.Min(numberOfRooms, 2) - 1)));
            List<Room> rooms = new List<Room>();
            int remainingX = dimensions.Width, remainingY = dimensions.Height, remainingArea = dimensions.GetArea();
            int maxArea = (int)(remainingArea * 0.667);
            for (int i = 0; i < numberOfRooms; i++)
            {
                var room = new Room(level, i.ToString(), wallArchitecture, rooms, dimensions);

                if (room.Place() && room.Resize())
                    rooms.Add(room);
            }

            // Position player inside a room
            var roll = Game.Roll(1, rooms.Count, true);
            var selectedRoomPerimeter = rooms[roll].InnerPerimeter;
            var loc = new Point(selectedRoomPerimeter.X + Game.Roll(1, selectedRoomPerimeter.Width, true),
                                selectedRoomPerimeter.Y + Game.Roll(1, selectedRoomPerimeter.Height, true));
            Game.IngameScreen.Player.MapBlock =
                level.GetMapBlockAt(loc);
            if (Game.IngameScreen.Player.LastAction == GameAction.Ascend)
            {
                level.LevelBelow = lastLevel;
                Game.IngameScreen.Player.MapBlock.Architecture = GameArchitecture.StairsDown;
            }
            else if (Game.IngameScreen.Player.LastAction == GameAction.Descend)
            {
                level.LevelAbove = lastLevel;
                Game.IngameScreen.Player.MapBlock.Architecture = GameArchitecture.StairsUp;
            }
            for (int i = 0; i < rooms.Count; i++)
            {
                Door sourceDoor = rooms[i].CreateDoor();

                Door destDoor = rooms[Game.Roll(1, rooms.Count, new[] { i }, true)].CreateDoor();

                //var corridor = new Corridor(GameArchitecture.StonePath, sourceDoor, destDoor);
                level.Corridors.Add(sourceDoor.CreateCorridorTo(destDoor));

            }
            level.Rooms = rooms;
            foreach (var room in rooms)
                room.AddToMap(level.map);
            foreach (var corridor in level.Corridors)
                corridor.AddToMap(level.map);
            //level.Corridors.Add(
            //    new Corridor(
            //        new Door(rooms[0], level.GetMapBlockAt(rooms[0].OuterPerimeter.Left, rooms[0].OuterPerimeter.Bottom)),
            //        new Door(rooms[1], level.GetMapBlockAt(rooms[1].OuterPerimeter.Left, rooms[1].OuterPerimeter.Bottom))));
            return level;
        }
        public static Level GenerateLevel2(string name, Rectangle dimensions, GameArchitecture initialBaseArchitecture, GameArchitecture baseArchitecture, GameArchitecture wallArchitecture, bool withStairsUp, bool withStairsDown, Level lastLevel)
        {
            var level = new Level(name, dimensions, initialBaseArchitecture) { BaseArchitecture = baseArchitecture };

            int numberOfRooms = (Game.Roll(14, 18) + 48) / 10;
            int minRoomSize = 4;
            Point maxRoomSize = new Point(dimensions.Width - (4 * (Math.Min(numberOfRooms, 2) - 1)),
                                          dimensions.Height - (2 * (Math.Min(numberOfRooms, 2) - 1)));
            List<Room> rooms = level.Rooms;
            var recs = CreateRooms(level);
            level.splitRectangles = recs;
            // Position player inside a room
            var roll = Game.Roll(1, rooms.Count, true);
            var selectedRoomPerimeter = rooms[roll].InnerPerimeter;
            var loc = new Point(selectedRoomPerimeter.X + Game.Roll(1, selectedRoomPerimeter.Width, true),
                                selectedRoomPerimeter.Y + Game.Roll(1, selectedRoomPerimeter.Height, true));
            Game.IngameScreen.Player.MapBlock =
                level.GetMapBlockAt(loc);
            if (Game.IngameScreen.Player.LastAction == GameAction.Ascend)
            {
                level.LevelBelow = lastLevel;
                Game.IngameScreen.Player.MapBlock.Architecture = GameArchitecture.StairsDown;
            }
            else if (Game.IngameScreen.Player.LastAction == GameAction.Descend)
            {
                level.LevelAbove = lastLevel;
                Game.IngameScreen.Player.MapBlock.Architecture = GameArchitecture.StairsUp;
            }
            for (int i = 0; i < rooms.Count; i++)
            {
               // Door sourceDoor = rooms[i].CreateDoor();

                //Door destDoor = rooms[Game.Roll(1, rooms.Count, new[] { i }, true)].CreateDoor();

                ////var corridor = new Corridor(GameArchitecture.StonePath, sourceDoor, destDoor);
                //level.Corridors.Add(sourceDoor.CreateCorridorTo(destDoor));

            }
            //level.Rooms = rooms;
            foreach (var room in rooms)
                room.AddToMap(level.map);
            foreach (var corridor in level.Corridors)
                corridor.AddToMap(level.map);
            //level.Corridors.Add(
            //    new Corridor(
            //        new Door(rooms[0], level.GetMapBlockAt(rooms[0].OuterPerimeter.Left, rooms[0].OuterPerimeter.Bottom)),
            //        new Door(rooms[1], level.GetMapBlockAt(rooms[1].OuterPerimeter.Left, rooms[1].OuterPerimeter.Bottom))));
            return level;
        }
        public static List<SplitRectangle>[] CreateRooms(Level level)
        {
            var splitRectangles = new List<SplitRectangle>[6];
            splitRectangles[0] = new List<SplitRectangle> { new SplitRectangle(null, level.Dimensions) };
            SplitLevel(splitRectangles, 1);
            foreach (var rectangle in splitRectangles.SelectMany(r => r).Where(r => !r.SubRectangles.Any()))
                level.Rooms.Add(rectangle.CreateRoom(level));
            for (int i = splitRectangles.Length - 1; i >= 0; i--)
            {
                
                foreach (var rec in splitRectangles[i].Where(r=>r.SubRectangles.Any()))
                {
                   if (rec.SubRectangles[0].Room != null && rec.SubRectangles[1].Room != null)
                   {
                       rec.SubRectangles[0].Room.JoinWith(rec.SubRectangles[1].Room);
                   }
                }

            }
            return splitRectangles;
        }
        // Recursively split level
        public static void SplitLevel(List<SplitRectangle>[] splitRectangles, int iteration)
        {
            var rectangles = new List<SplitRectangle>();
            foreach (var rectangle in splitRectangles[iteration - 1])
            {
                if (iteration < 5 || rectangle.Source.Width > 20 || rectangle.Source.Height > 20 || Game.Roll(1, 10) > 4 + (iteration - 5f) / 4 * 10)
                    rectangles.AddRange(rectangle.Split());
            }
            splitRectangles[iteration] = rectangles;
            iteration++;
            if (iteration < splitRectangles.Length)
                SplitLevel(splitRectangles, iteration);
        }
        # endregion
        # endregion
    }
}
